package com.javatest.redpacket.service.impl;

import com.javatest.redpacket.entities.UserRedPacket;
import com.javatest.redpacket.service.RedisRedPacketService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Created with IntelliJ IDEA.
 * Description:
 *
 * @author lzy
 * @date 2020-08-06 上午10:58
 */
@Service
public class RedisRedPacketServiceImpl implements RedisRedPacketService {
    private final static String PREFIX = "red_packet_list_";
    /**
     * 每次取出1000条,避免内存消耗过高
     */
    private static final int TIME_SIZE = 100;
    // lua脚本
    String script;
    // 在缓存lua脚本后,用该变量保存Redis返回的32位sha1编码，使用它取执行缓存的脚本
    String sha1;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private RedisRedPacketService redisRedPacketService;

    public RedisRedPacketServiceImpl() {
        StringBuilder builder = new StringBuilder();
        builder.append("local listKey = 'red_packet_list_'..KEYS[1]").append("\n");
        builder.append("local redPacket = 'red_packet_'..KEYS[1]").append("\n");
        builder.append("local stock = tonumber(redis.call('hget', redPacket, 'stock'))").append("\n");
        builder.append("if stock <= 0 then return 0 end").append("\n");
        builder.append("stock=stock-1").append("\n");
        builder.append("redis.call('hset',redPacket, 'stock',tostring(stock))").append("\n");
        builder.append("redis.call('rpush', listKey, ARGV[1])").append("\n");
        builder.append("if stock == 0 then return 2 end").append("\n");
        builder.append("return 1").append("\n");
        script = builder.toString();
    }

    /**
     * 开启新的线程执行
     * Save user red packet by redis.
     *
     * @param redPacketId the red packet id
     * @param unitAmount  the unit amount
     */
    @Async
    @Override
    public void saveUserRedPacketByRedis(Long redPacketId, Double unitAmount) {
        System.out.println("开始保存数据");
        long start = System.currentTimeMillis();
        //获取列表操作对象
        BoundListOperations ops = redisTemplate.boundListOps(PREFIX + redPacketId);
        Long size = ops.size();
        Long times = size % TIME_SIZE == 0 ? size / TIME_SIZE : size / TIME_SIZE + 1;
        int count = 0;
        List<UserRedPacket> userRedPacketList = new ArrayList<>(TIME_SIZE);
        for (int i = 0; i < times; i++) {
            // 获取至多TIME_SIZE个抢红包信息
            List userIdList = null;
            if (i == 0) {
                userIdList = ops.range(i * TIME_SIZE, (i + 1) * TIME_SIZE);
            } else {
                userIdList = ops.range(i * TIME_SIZE + 1, (i + 1) * TIME_SIZE);
            }
            userRedPacketList.clear();
            // 保存红包信息
            for (int j = 0; j < userIdList.size(); j++) {
                String args = userIdList.get(j).toString();
                String[] arr = args.split("-");
                String userIdStr = arr[0];
                String timeStr = arr[1];
                Long userId = Long.parseLong(userIdStr);
                Long time = Long.parseLong(timeStr);
                // 生产抢红包信息
                // 生成红包信息
                UserRedPacket userRedPacket = new UserRedPacket();
                userRedPacket.setRedPacketId(redPacketId);
                userRedPacket.setUserId(userId);
                userRedPacket.setAmount(unitAmount);
                userRedPacket.setGrabTime(new Timestamp(time));
                userRedPacket.setNote("抢红包" + redPacketId);
                userRedPacketList.add(userRedPacket);
            }
            // 插入抢红包信息
            count += executeBatch(userRedPacketList);
        }

        // 删除redis列表
        redisTemplate.delete(PREFIX + redPacketId);
        Long end = System.currentTimeMillis();
        System.out.println("保存数据结束，耗时" + (end - start) + "毫秒,共" + count + "条数据被保存");
    }

    @Override
    public Long grabRedPacketByRedis(Long redPacketId, Long userId) {
        // 当前抢红包用户和日期信息
        String args = userId + "-" + System.currentTimeMillis();
        Long result = null;
        // 获取Redis底层操作对象
        Jedis jedis = (Jedis) redisTemplate.getConnectionFactory().getConnection().getNativeConnection();
        try {
            //如果没有脚本加载过, 那么进行加载, 这样会返回一个sha1编码
            if (StringUtils.isEmpty(sha1)) {
                sha1 = jedis.scriptLoad(script);
            }
            //执行脚本返回结果
            final Object evalsha = jedis.evalsha(sha1, 1, String.valueOf(redPacketId), args);
            result = (Long) evalsha;
            // 返回2时为最后一个红包，此时通过异步方式将红包信息保存到数据库中
            if (result == 2) {
                // 获取单个小红包的金额
                String unitAmountStr = jedis.hget("red_packet_" + redPacketId, "unit_amount");
                // 触发保存数据库操作
                Double unitAmount = Double.valueOf(unitAmountStr);
                System.out.println("thread name :" + Thread.currentThread().getName());
                redisRedPacketService.saveUserRedPacketByRedis(redPacketId, unitAmount);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 确保jedis关闭
            if (Objects.nonNull(jedis) && jedis.isConnected()) {
                jedis.close();
            }
        }
        return result;
    }


    /**
     * Execute batch int.
     * 使用jdbc批量处理redis缓存
     *
     * @param userRedPacketList the user red packet list 抢红包列表
     * @return the int 抢红包插入数量
     */
    private int executeBatch(List<UserRedPacket> userRedPacketList) {
        Connection conn = null;
        Statement stmt = null;
        int[] count = null;
        try {
            conn = dataSource.getConnection();
            conn.setAutoCommit(false);
            stmt = conn.createStatement();
            for (UserRedPacket userRedPacket : userRedPacketList) {
                String sql = "update t_red_packet set stock=stock-1 where id=" + userRedPacket.getRedPacketId();
                DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                StringBuilder builder = new StringBuilder("insert into t_user_red_packet(red_packet_id, user_id, amount, grab_time, note) values(");
                builder.append(userRedPacket.getRedPacketId()).append(",");
                builder.append(userRedPacket.getUserId()).append(",");
                builder.append(userRedPacket.getAmount()).append(",");
                builder.append("'").append(df.format(userRedPacket.getGrabTime())).append("'").append(",");
                builder.append("'").append(userRedPacket.getNote()).append("'");
                stmt.addBatch(sql);
                stmt.addBatch(builder.toString());
            }
            // 执行批量
            count = stmt.executeBatch();
            // 提交事务
            conn.commit();
        } catch (SQLException e) {
            throw new RuntimeException("抢红包批量插入执行程序错误");
        } finally {
            try {
                if (Objects.nonNull(conn) && !conn.isClosed()) {
                    conn.close();
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }

        // 返回记录数
        return count.length / 2;
    }
}
